package cfar.actors;

import cfar.actors.distributed.Signal.Close;
import cfar.actors.distributed.Signal.StatusRequest;
import cfar.actors.distributed.Signal.Stop;
import cfar.ff.data.Maybe;

import cfar.ff.data.Unit;
import static cfar.ff.data.Maybe.*;
import static cfar.utils.IO.*;
import static cfar.actors.Signal.*;


import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author badi
 */
public abstract class RemoteActor extends Actor {

    private Maybe<Socket> client;
    private final Maybe<ServerSocket> socket;
    public final Maybe<InetAddress> my_address;
    public final Maybe<Integer> my_port;
    protected Signal status;
    protected final String my_address_string;

    public RemoteActor() {
        client = nothing();

        Maybe<InetAddress> address = nothing();
        Maybe<ServerSocket> sock = nothing();
        Maybe<Integer> port = nothing();
        String address_string = "";
        try {
            address = just(InetAddress.getLocalHost());
            sock = just(new ServerSocket(0));
            port = just(sock.from().getLocalPort());
            address_string = just(address.from() + "-" + port.from()) + "";
        } catch (UnknownHostException ex) {
            Logger.getLogger(RemoteActor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(RemoteActor.class.getName()).log(Level.SEVERE, null, ex);
        }
        my_address = address;
        socket = sock;
        my_port = port;
        my_address_string = address_string;

    }

    protected void run_actor() {
        if (socket.isJust) {
            try {
                Socket client = socket.from().accept();
                boolean running = true;
                do {

                    client = client.isClosed()
                            ? socket.from().accept()
                            : client;

                    Message msg = (Message) read_object(client.getInputStream());

                    if (msg.signal instanceof Stop) {
                        socket.from().close();
                        client.close();
                        cleanup();
                        running = false;
                    } else if (msg.signal instanceof Close) {
                        client.close();
                    } else if (msg.signal instanceof StatusRequest) {
                        write_object(new Message(status, Unit.unit()), null);
                    } else {
                        this.client = just(client);
                        act(msg);
                        this.client = nothing();
                    }
                } while (running);
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(RemoteActor.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(RemoteActor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    protected void act(Message msg) {
        if (this.client.isJust) {
            act(msg, this.client.from());
        }
    }

    protected void setStatus(Signal s){
        status = s;
    }

    /**
     * Called when the actor is terminated with {@link Signal.Stop}
     */
    protected abstract void cleanup();

    protected abstract void act(Message msg, Socket client);
}
